package cc.verywell.pureblock.msg.nfs;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.junit.jupiter.api.Disabled;

import cc.verywell.pureblock.func.node.KeyShell;
import cc.verywell.pureblock.log.Pblog;
import cc.verywell.pureblock.msg.nfs.map.FSmap;
import cc.verywell.pureblock.msg.nfs.map.FSmapHolding;
import cc.verywell.pureblock.msg.nfs.strc.NfsStructureV0;
import cc.verywell.pureblock.net.AioProvider;
import cc.verywell.pureblock.util.sign.RsaServer;
import cc.verywell.pureblock.util.unsafe.UnsafeRun;

/**
* 
* Copyright © 2019 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2019年4月22日 上午12:06:04
*/
@Deprecated
public class Cloudmap implements FSmap {
	final NfsControler nfs;
	final FSmapHolding fsmap;
	final Executor exec;
	
	public Cloudmap(NfsControler nfs,FSmapHolding fsmap,Executor exec) {
		this.nfs = nfs;
		this.fsmap = fsmap;
		this.exec = exec;
	}

	@Override
	public boolean put(KeyShell key, byte[] value) {
		if(fsmap.put(key, value)) {
			BiConsumer<KeyShell, byte[]> b = nfs.fireLocalFileAdded(key);
			if(b!=null)b.accept(key, value);

			value = null;//释放内存
			nfs.newStored(Collections.singletonMap(key, null));
			return true;
		}
		return false;
	}
	//新远程文件到达，不会检查nfs
	public boolean remotePut(KeyShell key, byte[] value) {
		return fsmap.put(key, value);
	}

	//没有远程删除机制
	@Override
	public boolean remove(KeyShell key) {
		return fsmap.remove(key);
	}
	@Override
	public boolean contain(KeyShell key) {
		return fsmap.contain(key);
	}

	@Override
	public byte[] get(KeyShell key) {
		byte[] payload = fsmap.get(key);
		if(payload==null) {
			nfs.newSeeking(null, 0, key);
		}
		return payload;
	}
	public void get(KeyShell key,BiConsumer<KeyShell,byte[]> func) {
		get(key, func, 0);
	}
	public void get(KeyShell key,BiConsumer<KeyShell,byte[]> func,long timeoutAdd) {
		byte[] payload = fsmap.get(key);
		if(payload==null) {
			nfs.newSeeking(func, timeoutAdd, key);
		}else {
			exec.execute(()->{ //强制异步
				func.accept(key, payload);
			});
		}
	}
	
	//输出内存控制
	static private abstract class HeadData{
		public ByteBuffer getByteBuffer(){return null;}
		public boolean hasByteBuffer(){return false;}
		public IOException err() {return null;}
	}
	static private class HasHeadData extends HeadData{
		final private NfsStructureV0.Sub e; 
		public HasHeadData(NfsStructureV0.Sub e) {
			this.e=e;
		};
		@Override
		public ByteBuffer getByteBuffer(){return e.getByteBuffer();}
		@Override
		public boolean hasByteBuffer(){return true;}
	}
	static private class ErrHeadData extends HeadData{
		final private IOException e; 
		public ErrHeadData(IOException e) {
			this.e=e;
		};
		@Override
		public IOException err() {return e;}
	}
	final static private HeadData EOF = new HeadData(){};
	final static private HeadData DOWNING = new HeadData(){};
	final static private HeadData LINK = new HeadData(){};
	/**
	* Copyright © 2019 TanYaqiu. All rights reserved.
	*/
	private class MemoryBuffer implements Closeable{
		final private long capacity;
		final public long timeoutAdd;
		final private KeyBuffer root;
		final private HashSet<KeyBuffer> getingList;
		final private Consumer<NfsStructureV0> inputsub;
		long used = 0;
		ErrHeadData err = null;
		boolean closed = false;
		
		public MemoryBuffer(byte[] key,long capacity,long timeoutAdd, Consumer<NfsStructureV0> inputsub) {
			this.capacity = capacity;
			this.timeoutAdd = timeoutAdd;
			this.root = new KeyBuffer(key,null);
			this.getingList = new HashSet<>();
			this.inputsub = inputsub;
			this.used = root.memoryUsed();
		}
		
		public synchronized void start() {
			if(closed) return;
			if(null!=err)return;
			if(canNewget()) {
				newget(root);
			}else if(getingList.isEmpty()) {
				HeadData hhd = getHeadData(root,false);
				if(hhd==DOWNING) {
					try {
						throw new IOException("NFScloud sinal err ["+Pblog.byteArray2Hex(root.key)+"]");
					} catch (IOException e) {
						this.err = new ErrHeadData(e);
					}
					return;
				}else if(hhd==LINK) {
					freeData(root,true);
					if(canNewget()) {
						newget(root);
						return;
					}
					freeAll(root,true);
					if(canNewget()) {
						newget(root);
						return;
					}
					try {
						throw new IOException("NFScloud no enough memory err ["+Pblog.byteArray2Hex(root.key)+"] "+this.used+"/"+this.capacity);
					} catch (IOException e) {
						this.err = new ErrHeadData(e);
					}
				}
			}
		}
		private synchronized void freeData(KeyBuffer kb,boolean isHead) {
			//此时canNewget==false;
			switch(kb.state) {
			case KeyBuffer.NO_DATA:
				if(kb.links!=null) {
					Iterator<KeyBuffer> it = kb.links.descendingIterator();
					while(it.hasNext()) {
						KeyBuffer kbsub = it.next();
						freeData(kbsub,(isHead && kbsub==kb.links.getFirst()));
						if(canNewget())return;
					}
				}
				if(!isHead) {
					if(!kb.pushedInputSub)fsmap.addHold(this, new KeyShell(kb.key));
					used -= kb.cutData();
				}
				return;
			case KeyBuffer.HAS_DATA:
				if(kb.links!=null) {
					Iterator<KeyBuffer> it = kb.links.descendingIterator();
					while(it.hasNext()) {
						KeyBuffer kbsub = it.next();
						freeData(kbsub,(isHead && kbsub==kb.links.getFirst()));
						if(canNewget())return;
					}
				}
				if(!isHead) {
					fsmap.addHold(this, new KeyShell(kb.key));
					used -= kb.cutData();
				}
				return;
			case KeyBuffer.LINK:
			case KeyBuffer.LOADING:
			case KeyBuffer.RELOAD_DATA:
			}
		}
		private synchronized void freeAll(KeyBuffer kb,boolean isHead) {
			//此时canNewget==false;
			switch(kb.state) {
			case KeyBuffer.NO_DATA:
				if(kb.links!=null) {
					Iterator<KeyBuffer> it = kb.links.descendingIterator();
					while(it.hasNext()) {
						KeyBuffer kbsub = it.next();
						freeData(kbsub,(isHead && kbsub==kb.links.getFirst()));
						if(canNewget())return;
					}
					if(!isHead) {
						fsmap.addHold(this, new KeyShell(kb.key));
						used -= kb.cutAll();
					}
				}
				return;
			case KeyBuffer.HAS_DATA:
				if(kb.links!=null) {
					Iterator<KeyBuffer> it = kb.links.descendingIterator();
					while(it.hasNext()) {
						KeyBuffer kbsub = it.next();
						freeData(kbsub,(isHead && kbsub==kb.links.getFirst()));
						if(canNewget())return;
					}
					fsmap.addHold(this, new KeyShell(kb.key));
					if(!isHead)used -= kb.cutAll();
					else used -= kb.cutData();
				}
				return;
			case KeyBuffer.LINK:
			case KeyBuffer.LOADING:
			case KeyBuffer.RELOAD_DATA:
			}
		}
		
		public synchronized HeadData getHeadData() {
			if(closed) {
				if(null==err)err = new ErrHeadData(new IOException("NFScloud closed ["+Pblog.byteArray2Hex(root.key)+"]"));
			}
			if(null!=err)return err;
			return getHeadData(root,true);
		}
		private synchronized HeadData getHeadData(KeyBuffer kb,boolean poll){
			switch(kb.state) {
			case KeyBuffer.LINK:
				return LINK;
			case KeyBuffer.LOADING:
			case KeyBuffer.RELOAD_DATA:
				return DOWNING;
			case KeyBuffer.NO_DATA:
				kb.pushSub(inputsub);
				used -= kb.cutData();
				if(kb.links!=null) {
					KeyBuffer kbsub;
					while(null!=(kbsub = kb.links.getFirst())) {
						HeadData hd = getHeadData(kbsub, poll);
						if(EOF == hd) {
							used -= KeyBuffer.linkNode(kb.links.removeFirst());
						}
						else return hd;
					}
					kb.links = null;
					used -= KeyBuffer.linkMem();
				}
				return EOF;
			case KeyBuffer.HAS_DATA:{
				kb.pushSub(inputsub);
				if(kb.links!=null) {
					KeyBuffer kbsub;
					while(null!=(kbsub = kb.links.getFirst())) {
						HeadData hd = getHeadData(kbsub, poll);
						if(EOF == hd) {
							used -= KeyBuffer.linkNode(kb.links.removeFirst());
						}
						else return hd;
					}
					kb.links = null;
					used -= KeyBuffer.linkMem();
				}
				if(kb.data!=null) {
					if(poll && kb.parent!=null) {
						if(kb.parent.links.remove(kb)) {
							used -= KeyBuffer.linkNode(kb);
						}
					}
					return new HasHeadData(kb.data.data);
				}
				return EOF;}
			default:
				throw new IllegalArgumentException("NFScloud unknow state "+kb.state+" ["+Pblog.byteArray2Hex(root.key)+"] at ["+Pblog.byteArray2Hex(kb.key)+"]");
			}
		}
		
		public synchronized void accept(KeyBuffer kb, byte[] data) {
			if(getingList.remove(kb)) {
				used -= AioProvider.FILE_MAX_SIZE;
			}
			if(closed)return;
			boolean isHead = true;
			KeyBuffer k = kb;
			int level = 0;
			tt:{
				if(k==root) {
					break tt;
				}
				KeyBuffer pk = k.parent;
				while(pk!=null) {
					if(pk.links==null)break; //之前释放了内存;
					level++;
					if(isHead) {
						isHead = k==pk.links.getFirst();
					}
					if(pk==root) {
						break tt;
					}
					k = pk;
					pk = k.parent;
				}
				if(Pblog.FS)Pblog.FS("NFScloud accept unknow ",kb.state," key:[",kb.key,"] at [",root.key,"]");
				start();
				return;
			}
			try {
				if(data==null)throw new IOException("NFScloud unReachable ["+Pblog.byteArray2Hex(root.key)+"] at level "+level+" ["+Pblog.byteArray2Hex(kb.key)+"]");
				used += kb.addData(new NfsStructureV0(data));
			} catch (IOException e) {
				this.err = new ErrHeadData(e);
			}
			if(isHead) {
				kb.pushSub(inputsub);
				if(kb.state==kb.NO_DATA)used -= kb.cutData();
				notify();
			}
			start();
		}
		private synchronized boolean canNewget() {
			return capacity - used >= AioProvider.FILE_MAX_SIZE;
		}
		private synchronized void newget(KeyBuffer kb) {
			//此时canNewget==true;
			switch(kb.state) {
			case KeyBuffer.LINK:
				if(getingList.add(kb)) {
					kb.state = KeyBuffer.LOADING;
					startget(kb);//此时可能 canNewget==false;
				}
				return;
			case KeyBuffer.LOADING:
				return;
			case KeyBuffer.NO_DATA:
				if(kb.links!=null) {
					for (KeyBuffer k : kb.links) {
						newget(k);//此时可能 canNewget==false;
						if(!canNewget())return;
					}
				}
				if(!kb.pushedInputSub && kb.data==null) {
					if(getingList.add(kb)) {
						kb.state = KeyBuffer.RELOAD_DATA;
						startget(kb);//此时可能 canNewget==false;
					}
				}
				return;
			case KeyBuffer.HAS_DATA:
				if(kb.links!=null) {
					for (KeyBuffer k : kb.links) {
						newget(k);//此时可能 canNewget==false;
						if(!canNewget())return;
					}
				}
				if(kb.data==null) {
					if(getingList.add(kb)) {
						kb.state = KeyBuffer.RELOAD_DATA;
						startget(kb);//此时可能 canNewget==false;
					}
				}
				return;
			case KeyBuffer.RELOAD_DATA:
				return;
			}
		}
		private synchronized void startget(final KeyBuffer kb) {
			used += AioProvider.FILE_MAX_SIZE;
			//此时可能 canNewget==false;
			Cloudmap.this.get(new KeyShell(kb.key), (k,b)->{
				accept(kb,b);
			}, this.timeoutAdd);
		}
		
		@Override
		public synchronized void close() {
			if(closed)return;
			closed = true;
			used -= root.cutAll();
			fsmap.removeHold(this);
		}
		
		@Override
		public String toString() {
			return "NFScloud ["+Pblog.byteArray2Hex(root.key)+"] "+used+"/"+capacity+(closed?" closed":"")+(err!=null?" err":"");
		}

	}
	/**
	 * 连续肝18小时,吐血神么的啊啊啊啊
	* Copyright © 2019 TanYaqiu. All rights reserved.
	*/
	static private class KeyBuffer{
		final static byte LINK = 0; ///未初始化的链接
		final static byte LOADING = 1; ///正在载入
		final static byte NO_DATA = 2; ///有末尾data数据link
		final static byte HAS_DATA = 3; ///无末尾data数据的link
		final static byte RELOAD_DATA = 4; ///无末尾data数据的link
		final public byte[] key;
		final public KeyBuffer parent;
		public byte state;
		public LinkedList<KeyBuffer> links;
		public NfsStructureV0 data;
		public boolean pushedInputSub = true;
		public KeyBuffer(byte[] key,KeyBuffer parent) {
			this.state = LINK;
			this.key = key;
			this.parent = parent;
		}
		/**
		 * 内存占用
		 * 认真对待,待有条件时反复测试一下;
		 */
		public long memoryUsed() {
			/* isLinkKey = 8bytes类型+1byte
			 * obj = 8bytes类型
			 * bb = 引用地址长度+(12bytes类型+4bytes填充+length)
			 * =33
			 * notInputSub = 8bytes类型+1byte
			 * =42 = 48
			 */
			long l = 48+UnsafeRun.MemoryAddressSize*4+key.length;
			if(links!=null) {
				l += linkMem();
				for (KeyBuffer kb : links) {
					l += linkNode(kb);
				}
			}
			if(data!=null)l+=data.readMemoryUsed();
			return l;
		}
		public static long linkMem() {
			return UnsafeRun.MemoryAddressSize*2+24;
		}
		public static long linkNode(KeyBuffer kb) {
			return UnsafeRun.MemoryAddressSize*3+8+kb.memoryUsed();
		}
		//内存释放
		public long cutData() {
			if(data==null)return 0;
			long cut = data.readMemoryUsed();
			data = null;
			if(Pblog.FS)Pblog.FS("NFScloud cutData state ",state," key:[",key,"]");
			return cut;
		}
		public long cutAll() {
			long l = cutData();
			if(links!=null) {
				l += linkMem();
				for (KeyBuffer kb : links) {
					l += linkNode(kb);
				}
				links = null;
				if(Pblog.FS)Pblog.FS("NFScloud cutAll state ",state," key:[",key,"]");
			}
			this.state = LINK;
			return l;
		}
		//数据到达;
		public long addData(NfsStructureV0 data) throws IOException{
			if(state==LOADING) {
				long l;
				if(data.data==null) {
					this.state = NO_DATA;
					l = 0;
				}else {
					this.data = data;
					this.state = HAS_DATA;
					l = data.readMemoryUsed();
				}
				if(data.links!=null) {
					for (NfsStructureV0.Link link : data.links) {
						if(NfsStructureV0.DIRECTLINK_PRIORITY==link.getPriority()) {
							if(links==null) {
								links = new LinkedList<>();
								l += linkMem();
							}
							KeyBuffer kb = new KeyBuffer(link.getHash(),this);
							links.add(kb);
							l += linkNode(kb);
						}
					}
				}
				return l;
			}else if(state==RELOAD_DATA) {
				if(data.data==null) {
					//throw new IOException("NFScloud reload data==null ["+Pblog.byteArray2Hex(key)+" ]");
					this.data = data;
					this.state = NO_DATA;
					return data.readMemoryUsed();
				}else {
					this.data = data;
					this.state = HAS_DATA;
					return data.readMemoryUsed();
				}
			}else {
				if(Pblog.FS)Pblog.FS("NFScloud add data at state ",state," key:[",key,"]");
			}
			return 0;
		}
		//pushSub;
		public void pushSub(Consumer<NfsStructureV0> inputsub) {
			if(pushedInputSub)return;
			pushedInputSub = true;
			if(inputsub!=null)inputsub.accept(data);
		}
	}

	//大文件锁定机制
//	public interface InputSub{
//		public void alias(NfsStructureV0.Sub sub);
//		public void link(NfsStructureV0.Link link);
//		public void sign(NfsStructureV0.Sign sign);
//	}
	public InputStream readBigFile(byte[] key, Consumer<NfsStructureV0> isub) {
		return readBigFile(key, 1024L*1024L*5, 0, isub); // 5M 一组的读
	}
	
	public InputStream readBigFile(byte[] key,long bufferSize,long timeoutAdd, Consumer<NfsStructureV0> inputsub) {
		MemoryBuffer mb = new MemoryBuffer(key, bufferSize, timeoutAdd, inputsub);
		InputStreamConsumer func = new InputStreamConsumer(mb);
		mb.start();
		return func;
	}
//	private int blocksLockable() {
//		return (int)( (fsmap.capacity()/AioProvider.FILE_MAX_SIZE/* *0.75f */)-fsmap.holdSize() );
//	};
	
	private class InputStreamConsumer extends InputStream{
		final MemoryBuffer mb;
		public InputStreamConsumer(MemoryBuffer mb) {
			this.mb = mb;
		}

		private ByteBuffer bb;
		private boolean done = false;
		public void getByteBuffer() throws InterruptedException,IOException{
			HeadData hd = mb.getHeadData();
			if(hd.err()!=null)throw new IOException(hd.err());
			try {
				if(EOF==hd) {
					done = true;
					return;
				}
				if(hd.hasByteBuffer()) {
					this.bb = hd.getByteBuffer();
					return;
				}
			}finally {
				mb.start();
			}
			
			synchronized (mb) {
				mb.wait(mb.timeoutAdd);
			}
			
			hd = mb.getHeadData();
			if(hd.err()!=null)throw new IOException(hd.err());
			if(EOF==hd) {
				done = true;
				return;
			}
			if(hd.hasByteBuffer()) {
				this.bb = hd.getByteBuffer();
				return;
			}
			throw new IOException("NFScloud timeout ["+Pblog.byteArray2Hex(mb.root.key)+"] "+mb.timeoutAdd);
		}

		@Override
		public int read() throws IOException {
			if(done)return -1;
			if(bb==null || !bb.hasRemaining())
				try {
					getByteBuffer();
				} catch (InterruptedException e) {
					throw new IOException(e);
				}
			return 0xFF & bb.get();
		}
		@Override
		public int read(byte[] b, int off, int len) throws IOException {
			if(done)return -1;
			int i = 0;
			while(len>0) {
				if(bb==null || !bb.hasRemaining())
					try {
						getByteBuffer();
					} catch (InterruptedException e) {
						throw new IOException(e);
					}
				if(done)return i>0?i:-1;
				int l = Math.min(len, bb.remaining());
				bb.get(b, off, l);
				i+=l;
				off+=l;
				len-=l;
			}
			return i;
		}
		
		@Override
		public void close() throws IOException {
			mb.close();
			super.close();
		}
	}
	
	//写入/////////////////////////////////////////////////////////////////////////////////////
	//NFS cannot share a file without making this copy into the repo
	//这个限制我迟早把它拆了;
	//暂时还没有找到一个很好的写入方法，
	//20200204武汉冠状肺炎疫期，问题将通过cc.verywell.pureblock.msg.nfs.idx.extraDownload类解决;
	
	//TODO
	public void writeBigFile(Path filePath) throws IOException {
//		try {
//			boolean isReadableFile = Files.isRegularFile(filePath) && Files.isReadable(filePath);
//			if(!isReadableFile)throw new IOException();
//		}catch(Exception e) {
//			throw new IOException("file ["+filePath+"] cannot see!",e);
//		}
//		long fileLenth = Files.size(filePath);
//		
//		
//		
//		if(fileLenth>fsmap.capacity()) {
//			throw new IOException("Key store file ["+filePath+"], size:"+fileLenth+" that is too big.");
//		}
//		try(AsynchronousFileChannel afc = AsynchronousFileChannel.open(filePath,StandardOpenOption.READ);){
//			if(Pblog.IO)Pblog.IO("read RSA key store from exists file.");
//			ByteBuffer bb = ByteBuffer.allocate((int)fileLenth);
//			Future<Integer> f = afc.read(bb, 0);
//			f.get();
//			bb.flip();
//			aes.decode(bb);
//			return RsaServer.load(bb);
//		}
	}
	public void readBigFile(KeyShell key,Path filePath) {
		//TODO
	}


	
	

}
